﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mogre;


namespace WastedMechanics
{
    class GameCamera
    {
        public enum CameraMode
        {
            Follow,
            Conversation
        }

        public Character Character;        
        public float Distance;        
        public float Softness = 0.2f;
        public CameraMode Mode;
        
        Vector3 InterlocutorHead;
        Vector3 CharacterHead;

        public void EnterConversationMode(SelectableObject interlocutor)
        {
            Mode = CameraMode.Conversation;
            InterlocutorHead =  interlocutor.Position 
                +(interlocutor is Character ? (interlocutor as Character).Profile.HeadOffset : Vector3.ZERO);
            CharacterHead = Character.Position + Character.Profile.HeadOffset;
        }

        public void LookAtCharacter()
        {
            float rand1 = (Mogre.Math.UnitRandom() > 0.5f ? 1 : -1);
            float rand2 = (Mogre.Math.UnitRandom() > 0.5f ? 1 : -1);
            Vector3 offset = Character.Orientation * new Vector3(1 * rand1, 0.2f * rand2, 1);           
            Raycaster.OnlyStatic raycast = new Raycaster.OnlyStatic();
            raycast.Go(Engine.NewtonWorld, CharacterHead, CharacterHead+offset);
            if (raycast.ClosestHit.HitDistance > 0)
                offset *= raycast.ClosestHit.HitDistance;
            Position = CharacterHead + offset;
            LookAt(CharacterHead); 
        }

        public void LookAtInterlocutor()
        {
            float rand1 = (Mogre.Math.UnitRandom() > 0.5f ? 1 : -1);
            float rand2 = (Mogre.Math.UnitRandom() > 0.5f ? 1 : -1);
            Vector3 offset = -(Character.Orientation * new Vector3(1 * rand1, 0.2f * rand2, 1));
            Raycaster.OnlyStatic raycast = new Raycaster.OnlyStatic();
            raycast.Go(Engine.NewtonWorld, InterlocutorHead, InterlocutorHead + offset);
            if (raycast.ClosestHit.HitDistance > 0)
                offset *= raycast.ClosestHit.HitDistance;
            Position = InterlocutorHead + offset;
            LookAt(InterlocutorHead);
        }

        public void ExitConversationMode()
        {
            Mode = CameraMode.Follow;
        }
          
        public Vector3 Direction
        {
            get
            {
                switch (Mode)
                {
                    case CameraMode.Follow:
                        return Character.LookDirection;
                    case CameraMode.Conversation:
                        return Engine.Camera.Direction;
                }
                return Vector3.ZERO; //compiler doesn't know that ALL paths returns value...
            }
            set
            {
                if (Mode == CameraMode.Conversation)
                    Engine.Camera.Direction = value;
            }
        }

        public Vector3 Position
        {
            get
            {
                return Engine.Camera.Position;
            }
            set
            {
                if (Mode == CameraMode.Conversation)
                    Engine.Camera.Position = value;
            }
        }

        public void Update()
        {
            switch (Mode)
            {
                case CameraMode.Follow:
                    {
                        Vector3 pivot = Character.Position + Character.Orientation * Character.Profile.AimOffset;

                        Engine.CameraVelocity = (FindDesiredPosition() - Engine.Camera.Position) * Softness;
                        Engine.Camera.Position += Engine.CameraVelocity;

                        Engine.Camera.LookAt(pivot);
                        break;
                    }
                case CameraMode.Conversation:
                    {
                        Engine.CameraVelocity = Vector3.ZERO;                        
                        break;
                    }
            }

        }

        public Vector3 FindDesiredPosition()
        {
            Vector3 offset = -Direction * Distance * 2;
            Vector3 pivot = Character.Position + Character.Orientation * Character.Profile.AimOffset;  
            Vector3 desiredPosition = Vector3.ZERO;

            Raycaster.OnlyStatic raycast = new Raycaster.OnlyStatic();
            raycast.Go(Engine.NewtonWorld, Character.Position, pivot);
            if (raycast.ClosestHit.HitDistance > 0)
            {
                float dotp = (raycast.ClosestHit.HitNormal).DotProduct(Direction);
                offset = Character.Position + (pivot - Character.Position) * (raycast.ClosestHit.HitDistance - 0.15f * dotp) - pivot;
            }
            else
            {
                raycast.Go(Engine.NewtonWorld, pivot, pivot + offset);

                if (raycast.ClosestHit.HitDistance > 0)
                {
                    Vector3 hitOffset = offset * (raycast.ClosestHit.HitDistance);
                    float dotp = (-hitOffset).CrossProduct(0.15f * raycast.ClosestHit.HitNormal).Length;
                    offset = hitOffset - hitOffset.NormalisedCopy * dotp;
                }                
            }
            
            offset = offset.NormalisedCopy * System.Math.Min(Distance, offset.Length);

            return pivot + offset;
        }

        public void LookAt(Vector3 pos)
        {
            Engine.Camera.LookAt(pos);
        }
    }
}
